#include "Subsystems/BlockBehaviors/SubsystemGrassBlockBehavior.h"
#include "GameRandom.hpp"
#include "Subsystems/SubsystemTimeOfDay.h"
#include "Subsystems/SubsystemTerrain.h"
#include "Subsystems/SubsystemGameInfo.h"
#include "Managers/BlocksManager.h"
#include "Managers/PlantsManager.h"
#include "Log.h"
#include "GameSingleton.h"

#include "Blocks/AirBlock.h"
#include "Blocks/IvyBlock.h"
#include "Blocks/FluidBlock.h"
#include "Blocks/GrassBlock.h"
#include "Blocks/DirtBlock.h"
#include "Blocks/SnowBlock.h"

namespace PocketSurvival
{

    SubsystemGrassBlockBehavior::SubsystemGrassBlockBehavior()
    {
        m_handleBlocks.push_back(GrassBlock::Index());
    }

    void SubsystemGrassBlockBehavior::onPoll(int32_t value, int32_t x, int32_t y, int32_t z)
    {
        if (Terrain::ExtractData(value) != 0 || m_gameInfo->environmentBehaviorMode != EnvironmentBehaviorMode::Living)
        {
            return;
        }
        int32_t num = Terrain::ExtractLight(m_terrain->getCellValue(x, y + 1, z));
        if (num == 0)
        {
            m_toUpdateLock.lock();
            m_toUpdate.emplace(Point3(x, y, z), Terrain::ReplaceContents(value, GrassBlock::Index()));
            m_toUpdateLock.unlock();

        }
        if (num < 13)
        {
            return;
        }
        for (int32_t i = x - 1; i <= x + 1; i++)
        {
            for (int32_t j = z - 1; j <= z + 1; j++)
            {
                for (int32_t k = y - 2; k <= y + 1; k++)
                {
                    int32_t cellValue = m_terrain->getCellValue(i, k, j);
                    if (Terrain::ExtractContents(cellValue) != DirtBlock::Index())
                    {
                        continue;
                    }
                    int32_t cellValue2 = m_terrain->getCellValue(i, k + 1, j);
                    if (killsGrassIfOnTopOfIt(cellValue2) || Terrain::ExtractLight(cellValue2) < 13 || !(GameSingleton::gameRandom->getFloat(0.0f, 1.0f) < 0.1f))
                    {
                        continue;
                    }
                    int32_t num2 = Terrain::ReplaceContents(cellValue, 8);

                    m_toUpdateLock.lock();
                    m_toUpdate.emplace(Point3(i, k, j), num2);
                    m_toUpdateLock.unlock();

                    if (Terrain::ExtractContents(cellValue2) == 0)
                    {
                        int32_t temperature = m_terrain->getTemperature(i, j);
                        int32_t humidity = m_terrain->getHumidity(i, j);
                        Random *random = GameSingleton::gameRandom;
                        int32_t num3 = GameSingleton::plantsManager->generateRandomPlantValue(*random, num2, temperature, humidity, k + 1);
                        if (num3 != 0)
                        {
                            m_toUpdateLock.lock();
                            m_toUpdate.emplace(Point3(i, k + 1, j), num3);
                            m_toUpdateLock.unlock();
                        }
                    }
                }
            }
        }
    }

    void SubsystemGrassBlockBehavior::onNeighborBlockChanged(int32_t x, int32_t y, int32_t z, int32_t neighborX, int32_t neighborY, int32_t neighborZ)
    {
        int32_t cellValue = m_terrain->getCellValue(x, y + 1, z);
        if (Terrain::ExtractContents(cellValue) == SnowBlock::Index())
        {
            int32_t cellValueFast = m_terrain->getCellValue(x, y, z);
            cellValueFast = Terrain::ReplaceData(cellValueFast, 1);
            m_terrain->changeCell(x, y, z, cellValueFast);
        }
        else
        {
            int32_t cellValueFast2 = m_terrain->getCellValue(x, y, z);
            cellValueFast2 = Terrain::ReplaceData(cellValueFast2, 0);
            m_terrain->changeCell(x, y, z, cellValueFast2);
        }
        if (killsGrassIfOnTopOfIt(cellValue))
        {
            m_terrain->changeCell(x, y, z, Terrain::MakeBlockValue(DirtBlock::Index(), 0, 0));
        }
    }

    void SubsystemGrassBlockBehavior::onExplosion(int32_t value, int32_t x, int32_t y, int32_t z, float damage)
    {
        if (damage > GameSingleton::blocksManager->blocks[GrassBlock::Index()]->ExplosionResilience * GameSingleton::gameRandom->getFloat(0.0f, 1.0f))
        {
            m_terrain->changeCell(x, y, z, Terrain::MakeBlockValue(DirtBlock::Index(), 0, 0));
        }
    }

    void SubsystemGrassBlockBehavior::update(float dt)
    {
        if (m_timeOfDay->periodicGameTimeEvent(80.0f, 0.0f) == true)
        {
            std::lock_guard<SpinLock> lk(m_toUpdateLock);
            for (const auto &pair : m_toUpdate)
            {
                if (Terrain::ExtractContents(pair.second) == GrassBlock::Index())
                {
                    int32_t oldValue = m_terrain->getCellValue(pair.first);
                    if(Terrain::ExtractContents(oldValue) == DirtBlock::Index())
                    {
                        m_terrain->changeOldCell(pair.first, oldValue, pair.second);
                    }
                }
                else
                {
                    int32_t oldValue = m_terrain->getCellValue(pair.first);
                    int32_t downContents = m_terrain->getCellContents(pair.first.posX, pair.first.posY - 1, pair.first.posZ);
                    if((downContents == GrassBlock::Index() || downContents != DirtBlock::Index()) && Terrain::ExtractContents(oldValue) == AirBlock::Index())
                    {
                        m_terrain->changeOldCell(pair.first, oldValue, pair.second);
                    }
                }
            }
            m_toUpdate.clear();
        }
    }

    bool SubsystemGrassBlockBehavior::killsGrassIfOnTopOfIt(int32_t value)
    {
        int32_t num = Terrain::ExtractContents(value);
        Block *block = GameSingleton::blocksManager->blocks[num];
        if (block->isBlock<FluidBlock *>() == false)
        {
            if (false == block->isFaceTransparent(5, value))
            {
                return block->IsCollidable;
            }
            return false;
        }
        return true;
    }

    void SubsystemGrassBlockBehavior::load(const nlohmann::json &json)
    {
        m_timeOfDay = GameSingleton::singletonPtr->getSingleton<SubsystemTimeOfDay>();
        m_terrain = GameSingleton::singletonPtr->getSingleton<SubsystemTerrain>();
        m_gameInfo = GameSingleton::singletonPtr->getSingleton<SubsystemGameInfo>();
    }

    static const std::string subsystemName = "GrassBlockBehavior";
    const std::string &SubsystemGrassBlockBehavior::getName() const
    {
        return subsystemName;
    }
    SubUpdateType SubsystemGrassBlockBehavior::getUpdateType()
    {
        return SubUpdateType::MultiThreadUpdate;
    }
}
